/*
* Copyright (c) Microsoft Corporation.  All Rights Reserved.
*
* Public Mock10 API
*/
#pragma once
#include "Mock10.h"

#include "ExternalMockFunction.h"
#include "MockMethod.h"

#pragma push_macro("Assert")
#undef Assert

#pragma push_macro("SEALED")
#undef SEALED

#if _MSC_VER >= 1400
# define SEALED sealed
#else
# define SEALED
#endif

namespace Mock10
{
    template <typename TPrototype>
    class ExternalMockMethod;

    namespace Private
    {
        template <typename TParameterTypes, typename TDetourPrototype, unsigned short ParameterCount>
        struct ExternalDetourMethodPrototypeImpl;
    
        #define EXTERNAL_DETOUR_METHOD_IMPL(PARAMCOUNT) \
        template <typename TPrototype, typename TDetourPrototype> \
        struct ExternalDetourMethodPrototypeImpl<TPrototype, TDetourPrototype, PARAMCOUNT> \
        { \
        public: \
            typedef typename WEX::Common::ParameterTypes<TDetourPrototype> TParameterTypes; \
            typedef typename TParameterTypes::ReturnType (Prototype) (typename ::Mock10::ExternalMockMethod<TPrototype>::Info& BUILD_VARIABLE_NAME(DETOUR_METHOD_NAMED_ARGS_, PARAMCOUNT)); \
        };

        EXTERNAL_DETOUR_METHOD_IMPL(0);
        EXTERNAL_DETOUR_METHOD_IMPL(1);
        EXTERNAL_DETOUR_METHOD_IMPL(2);
        EXTERNAL_DETOUR_METHOD_IMPL(3);
        EXTERNAL_DETOUR_METHOD_IMPL(4);
        EXTERNAL_DETOUR_METHOD_IMPL(5);
        EXTERNAL_DETOUR_METHOD_IMPL(6);
        EXTERNAL_DETOUR_METHOD_IMPL(7);
        EXTERNAL_DETOUR_METHOD_IMPL(8);
        EXTERNAL_DETOUR_METHOD_IMPL(9);
        EXTERNAL_DETOUR_METHOD_IMPL(10);
        EXTERNAL_DETOUR_METHOD_IMPL(11);
        EXTERNAL_DETOUR_METHOD_IMPL(12);
        EXTERNAL_DETOUR_METHOD_IMPL(13);
        EXTERNAL_DETOUR_METHOD_IMPL(14);
        EXTERNAL_DETOUR_METHOD_IMPL(15);
        EXTERNAL_DETOUR_METHOD_IMPL(16);
        EXTERNAL_DETOUR_METHOD_IMPL(17);
        EXTERNAL_DETOUR_METHOD_IMPL(18);
        EXTERNAL_DETOUR_METHOD_IMPL(19);
        EXTERNAL_DETOUR_METHOD_IMPL(20);

        template <typename TPrototype>
        struct ExternalDetourMethodPrototype SEALED : public ExternalDetourMethodPrototypeImpl<TPrototype, 
                                                                                               typename Private::MockMethodPrototype<TPrototype>::DetourPrototype,
                                                                                               WEX::Common::ParameterTypes<typename Private::MockMethodPrototype<TPrototype>::DetourPrototype>::ParameterCount>
        {
        };

        template <typename TPrototype, typename TDetourPrototype>
        class ExternalMockMethodInstance: public ExternalMockFunctionInstanceImpl<MockMethod<TPrototype, 
                                                                                             TDetourPrototype, 
                                                                                             ExternalDetourFunction<TPrototype, 
                                                                                                                    TDetourPrototype, 
                                                                                                                    typename ExternalMockMethod<TPrototype>::Info>>>
        {
        template <typename TPrototype, typename TDetourPrototype, typename TMockImpl>
        friend class ExternalMockFunctionImpl;

        private:
            explicit ExternalMockMethodInstance(const void* pExternalMockFunctionInstanceParameters, ExternalMockManager& mockManager)
                : ExternalMockFunctionInstanceImpl(pExternalMockFunctionInstanceParameters, mockManager)
            {
            }
        };
    }

    template <typename TPrototype>
    class ExternalMockMethod SEALED
    {
    public:
        class Info;
        typedef typename Private::ExternalDetourMethodPrototype<TPrototype>::Prototype TDetourPrototype;

        class Info
        {
        friend class Private::ExternalDetourFunction<TPrototype, TDetourPrototype, Info>;
        typedef typename Private::ExternalMockMethodInstance<TPrototype, TDetourPrototype>::TDetourFunctionImpl TDetourFunctionImpl;

        public:
            typename MockMethod<TPrototype, TDetourPrototype, TDetourFunctionImpl>::Target GetTarget() const
            {
                return MockMethod<TPrototype, TDetourPrototype, TDetourFunctionImpl>::Target(m_detourFunction.GetTarget(), m_detourFunction.GetThisAdjust());
            }

            template <typename TData>
            HRESULT SetValue(const wchar_t* pszName, const TData& value)
            {
                COMPILE_TIME_CHECK_V2(__is_pod(TData) || !__is_class(TData),
                        "TData arguments passed to SetValue must be PODs (plain old data types).", 
                        TData_arguments_passed_to_SetValue_must_be_PODs_aka_plain_old_data_types);

                return m_pExternalMockFunctionInstance->SetValue(pszName, reinterpret_cast<const unsigned char*>(&value), sizeof(TData));
            }

            template <typename TData>
            HRESULT GetValue(const wchar_t* pszName, __out TData& value)
            {
                COMPILE_TIME_CHECK_V2(__is_pod(TData) || !__is_class(TData),
                        "TData arguments passed to GetValue must be PODs (plain old data types).", 
                        TData_arguments_passed_to_GetValue_must_be_PODs_aka_plain_old_data_types);

                return m_pExternalMockFunctionInstance->GetValue(pszName, reinterpret_cast<unsigned char*>(&value), sizeof(TData));
            }

        private:
            Info(const TDetourFunctionImpl& detourFunction, Private::ExternalMockFunctionInstanceBase* pExternalMockFunctionInstance)
                : m_detourFunction(detourFunction)
                , m_pExternalMockFunctionInstance(pExternalMockFunctionInstance)
            {
            }

            Info(const Info&); // not implemented
            Info& operator=(Info&); // not implemented

            const TDetourFunctionImpl& m_detourFunction;
            Private::ExternalMockFunctionInstanceBase* m_pExternalMockFunctionInstance;
        };

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, TPrototype targetFunction)
            : m_impl(externalMockTarget, Private::MemberAsPointerToPointer<TPrototype>::Result(targetFunction))
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, TPrototype targetFunction, const wchar_t* pszFilter)
            : m_impl(externalMockTarget, Private::MemberAsPointerToPointer<TPrototype>::Result(targetFunction), pszFilter)
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, TPrototype targetFunction, TDetourPrototype* pDetourFunction)
            : m_impl(externalMockTarget, Private::MemberAsPointerToPointer<TPrototype>::Result(targetFunction), pDetourFunction)
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, TPrototype targetFunction, TDetourPrototype* pDetourFunction, const wchar_t* pszFilter)
            : m_impl(externalMockTarget, Private::MemberAsPointerToPointer<TPrototype>::Result(targetFunction), pDetourFunction, pszFilter)
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, unsigned long expectedMatchCount = 1)
            : m_impl(externalMockTarget, pszTargetBinary, pszTargetQuery, expectedMatchCount)
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                             const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
            : m_impl(externalMockTarget, pszTargetBinary, pszTargetQuery, pszFilter, expectedMatchCount)
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, TDetourPrototype* pDetourFunction,
                             unsigned long expectedMatchCount = 1)
            : m_impl(externalMockTarget, pszTargetBinary, pszTargetQuery, pDetourFunction, expectedMatchCount)
        {
        }

        ExternalMockMethod(const ExternalMockTarget& externalMockTarget, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, TDetourPrototype* pDetourFunction, 
                             const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
            : m_impl(externalMockTarget, pszTargetBinary, pszTargetQuery, pDetourFunction, pszFilter, expectedMatchCount)
        {
        }

        void Set(TDetourPrototype* pDetourFunction)
        {
            m_impl.Set(pDetourFunction);
        }

        // This version of Set is only here to help provide detailed compiler errors when attempting to set invalid detour functions
        static void Set(void* p)
        {
            Private::ExternalMockFunctionImpl<TPrototype, TDetourPrototype, Private::ExternalMockMethodInstance<TPrototype, TDetourPrototype>>::Set(p);
        }

        void Clear()
        {
            m_impl.Clear();
        }

        void SetFilter(const wchar_t* pszFilter)
        {
            m_impl.SetFilter(pszFilter);
        }

        HRESULT GetStatus() const
        {
            return m_impl.GetErrorCode();
        }

        const unsigned short* GetStatusMessage() const // Return unsigned short* here; otherwise we'd need to create both wchar_t* and __wchar_t* versions that return the value as an out parameter
        {
            return m_impl.GetMessage();
        }

        template <typename TData>
        HRESULT SetValue(const wchar_t* pszName, const TData& value)
        {
            COMPILE_TIME_CHECK_V2(__is_pod(TData) || !__is_class(TData),
                    "TData arguments passed to SetValue must be PODs (plain old data types).", 
                    TData_arguments_passed_to_SetValue_must_be_PODs_aka_plain_old_data_types);

            return m_impl.SetValue(pszName, reinterpret_cast<const unsigned char*>(&value), sizeof(TData));
        }

        template <typename TData>
        HRESULT GetValue(const wchar_t* pszName, TData& value)
        {
            COMPILE_TIME_CHECK_V2(__is_pod(TData) || !__is_class(TData),
                    "TData arguments passed to GetValue must be PODs (plain old data types).", 
                    TData_arguments_passed_to_GetValue_must_be_PODs_aka_plain_old_data_types);

            return m_impl.GetValue(pszName, reinterpret_cast<unsigned char*>(&value), sizeof(TData));
        }

    private:
        Private::ExternalMockFunctionImpl<TPrototype, TDetourPrototype, Private::ExternalMockMethodInstance<TPrototype, TDetourPrototype>> m_impl;
    };

    // This template specialization helps 'guide' the compiler to the correct Mock::ExternalFunction overloads when function queries are specified
    template <>
    class ExternalMockMethod<const wchar_t> SEALED
    {
    typedef void TDetourPrototype;

    public:
        template <typename T>
        ExternalMockMethod(const T&, ...)
        {
            // Make sure to specify TPrototype in your call to Mock::ExternalFunction<TPrototype(pszTargetBinary, pszQuery, ...)
            COMPILE_TIME_CHECK_V2(false,
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);
        }
    };
} /* namespace Mock10 */

#pragma pop_macro("SEALED")
#pragma pop_macro("Assert")
